package suery.syntax;

import java.util.*;

import suery.*;

/**
 * Syntax Engine is used to parse the expression and then find out the
 * components the user queried.
 * 
 * @author Jeky
 * 
 */
public class SyntaxEngine {

    /**
     * parse the expression and then find out the components the user queried.
     * 
     * @param exp expression
     * @param tree component tree
     * @return list of component tree nodes
     */
    public List<ComponentTreeNode> parse(String exp, ComponentTree tree) {
        Queue<String> tokenQueue = initTokenQueue(exp);
        List<ComponentTreeNode> resultList = new LinkedList<ComponentTreeNode>();
        resultList.addAll(parseQueue(tokenQueue, tree));
        return resultList;
    }

    private Queue<String> initTokenQueue(String exp) {
        Queue<String> tokenQueue = new LinkedList<String>();
        StringBuilder buffer = new StringBuilder();

        for (int i = 0; i < exp.length(); i++) {
            char ch = exp.charAt(i);
            switch (ch) {
                case '[':
                case ']':
                case '.':
                case '/':
                    if (buffer.length() != 0) {
                        tokenQueue.add(buffer.toString());
                        buffer = new StringBuilder();
                    }
                    tokenQueue.add(Character.toString(ch));
                    break;
                default:
                    checkIllegal(ch);
                    buffer.append(ch);
            }
        }
        if (buffer.length() != 0) {
            tokenQueue.add(buffer.toString());
        }
        return tokenQueue;
    }

    private void checkIllegal(char ch) {
        if (Character.isLetter(ch)
                || Character.isDigit(ch)
                || ch == '#'
                || ch == '$'
                || ch == ' ') {
            return;
        }
        throw new SueryException.Parse("" + ch);
    }

    private Set<ComponentTreeNode> parseQueue(Queue<String> tokenQueue,
                                              ComponentTree tree) {
        if (handlerMap == null) {
            initHandlerMap();
        }
        Set<ComponentTreeNode> parents = new HashSet<ComponentTreeNode>();
        Set<ComponentTreeNode> results = new HashSet<ComponentTreeNode>();
        parents.add(tree.getRoot());

        while (!tokenQueue.isEmpty()) {
            String token = tokenQueue.peek();
            TokenParser handler = defaultHandler;
            if (handlerMap.containsKey(token)) {
                handler = handlerMap.get(tokenQueue.poll());
            } else if (token.startsWith("#")) {
                handler = handlerMap.get("#");
            }
            handler.parse(tokenQueue, parents, results);
            if (results.isEmpty()) {
                throw new SueryException("Cannot find token : " + token);
            }
            parents = results;
            results = new HashSet<ComponentTreeNode>();
        }
        return parents;
    }

    private void initHandlerMap() {
        handlerMap = new HashMap<String, TokenParser>();
        handlerMap.put("#", new SearchByNameParser());
        handlerMap.put("/", new GetChildrenParser());
        handlerMap.put(".", new GetFieldParser());
        handlerMap.put("[", new IndexParser());
        defaultHandler = new SearchByClassParser();
    }
    private Map<String, TokenParser> handlerMap;
    private TokenParser defaultHandler;
}
